﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Security.Permissions;
using JQuery.Net.Modules;
using Newtonsoft.Json.Linq;

namespace JQuery.Net.UI
{
    [AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
    [DefaultProperty("RenderTemplate"), DefaultEvent("ClientCallback")]
    [ToolboxData("<{0}:AutoComplete runat=server></{0}:AutoComplete>")]
    public class AutoComplete
        : jQNetControl
        , INamingContainer
        , IEditableTextControl
        , IjQNetCallbackHandler
    {
        #region "fields"
        private readonly static object callback_key = new object();
        private readonly static object change_key = new object();
        private readonly static jQNetEvents __source_event = jQNetEvents.source;
        private readonly static jQNetEvents __select_event = jQNetEvents.select;
        private readonly static jQNetEvents __search_event = jQNetEvents.search;
        private readonly static jQNetEvents __open_event = jQNetEvents.open;
        private readonly static jQNetEvents __focus_event = jQNetEvents.focus;
        private readonly static jQNetEvents __close_event = jQNetEvents.close;
        private readonly static jQNetEvents __change_event = jQNetEvents.change;
        #endregion

        #region "constructor"
        public AutoComplete()
            : base(HtmlTextWriterTag.Input)
        {
            Text = String.Empty;
            TemplateID = String.Empty;
            Length = 1; /// initial Length
            Delay = 300;/// initial Delay

            Attributes["type"] = "text"; // this is a text input
        }
        #endregion

        #region "properties"
        [Bindable(true), Category("Appearance"), Description("The initial text of the Auto Complete Text Box.")]
        public Unit Width { get; set; }
        /// <summary>
        /// Get or Set the initial text of the Auto Complete Text Box.
        /// </summary>
        [Bindable(true), Category("Appearance"), Description("The initial text of the Auto Complete Text Box.")]
        public string Text { get; set; }
        /// <summary>
        /// Get or Set the minimum number of characters a user has to type before the Autocomplete activates.
        /// </summary>
        /// <remarks>
        /// Zero is useful for local data with just a few items. 
        /// Should be increased when there are a lot of items, 
        /// where a single character would match a few thousand items.
        /// </remarks>
        [Bindable(true), Category("Behavior"), Description("The minimum number of characters a user has to type before the Autocomplete activates.")]
        public int Length { get; set; }
        /// <summary>
        /// Get or Set the delay in milliseconds the Autocomplete waits after a keystroke to activate itself.
        /// </summary>
        /// <remarks>
        /// A zero-delay makes sense for local data (more responsive), 
        /// but can produce a lot of load for remote data, 
        /// while being less responsive.
        /// </remarks>
        [Bindable(true), Category("Behavior"), Description("The delay in milliseconds the Autocomplete waits after a keystroke to activate itself.")]
        public int Delay { get; set; }
        [Bindable(true), Category("Behavior"), Description("The ID of the render template to use for this auto complete.")]
        public string TemplateID { get; set; }
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public override List<Script> Templates
        {
            get
            {
                return base.Templates;
            }
            set
            {
                base.Templates = value;
            }
        }
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public override List<Script> ScriptEvents
        {
            get
            {
                return base.ScriptEvents;
            }
            set
            {
                base.ScriptEvents = value;
            }
        }
        #region "script properties"
        /// <summary>
        /// client function that implements two arguments: request and response
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptSource
        {
            get
            {
                return Scripts[__source_event].IsNotNullOrEmpty()
                    ? Scripts[__source_event]
                    : undefined;
            }
            set
            {
                Scripts[__source_event] = value;
            }
        }
        /// <summary>
        /// client function that implements the select event: 
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptSelect
        {
            get
            {
                return Scripts[__select_event].IsNotNullOrEmpty()
                    ? Scripts[__select_event]
                    : undefined;
            }
            set
            {
                Scripts[__select_event] = value;
            }
        }
        /// <summary>
        /// client function that implements the select event: 
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptSearch
        {
            get
            {
                return Scripts[__search_event].IsNotNullOrEmpty()
                    ? Scripts[__search_event]
                    : undefined;
            }
            set
            {
                Scripts[__search_event] = value;
            }
        }
        /// <summary>
        /// client function that implements the open event: 
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptOpen
        {
            get
            {
                return Scripts[__open_event].IsNotNullOrEmpty()
                    ? Scripts[__open_event]
                    : undefined;
            }
            set
            {
                Scripts[__open_event] = value;
            }
        }
        /// <summary>
        /// client function that implements the focus event: 
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptFocus
        {
            get
            {
                return Scripts[__focus_event].IsNotNullOrEmpty()
                    ? Scripts[__focus_event]
                    : undefined;
            }
            set
            {
                Scripts[__focus_event] = value;
            }
        }
        /// <summary>
        /// client function that implements the focus event: 
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptClose
        {
            get
            {
                return Scripts[__close_event].IsNotNullOrEmpty()
                    ? Scripts[__close_event]
                    : undefined;
            }
            set
            {
                Scripts[__close_event] = value;
            }
        }
        /// <summary>
        /// client function that implements the focus event: 
        /// </summary>
        [Bindable(false), Browsable(false)]
        public string ClientScriptChange
        {
            get
            {
                return Scripts[__change_event].IsNotNullOrEmpty()
                    ? Scripts[__change_event]
                    : undefined;
            }
            set
            {
                Scripts[__change_event] = value;
            }
        }
        #endregion
        #endregion

        #region "events"
        public event EventHandler TextChanged
        {
            add { Events.AddHandler(change_key, value); }
            remove { Events.RemoveHandler(change_key, value); }
        }
        public event jQNetHandler ClientCallback
        {
            add { Events.AddHandler(callback_key, value); }
            remove { Events.RemoveHandler(callback_key, value); }
        }
        #endregion

        #region "protected members"
        protected override bool ProcessPostData(NameValueCollection data)
        {
            bool __IsChanged = Text.Equals(data[UniqueID]) == false;

            Text = data[UniqueID];

            return __IsChanged;
        }

        public override void RaisePostDataChangedEvent()
        {   /// the text has changed we need to process the text changed event.
            var handler = (EventHandler)Events[change_key];

            if (Arguments != null)
            {
                Arguments.Cancel = handler == null;
            }

            if (handler != null)
            {
                handler(this, Page.IsCallback ? Arguments : new EventArgs());
            }
        }

        public override void RegisterJQNScriptInclude(ResourceManager Manager, JQNRegistration options)
        {
            options.RegisterJQNUIMouse = false;  /// we do not need the mouse features

            base.RegisterJQNScriptInclude(Manager, options);
#if DEBUG
            if (Manager.IsResourceRegistered(Resources.JQN.ui_auto_complete_debug) == false)
            {
                Manager.Add(JQNModule.Resources[Resources.JQN.ui_auto_complete_debug]);
            }
#else
            if(Manager.IsResourceRegistered(Resources.JQuery.ui_auto_complete) == false)
            {
                Manager.Add(JQueryModule.Resources[Resources.JQuery.ui_auto_complete]);
            }
#endif
        }

        public override void RegisterJQNScriptStartup(System.IO.StringWriter writer)
        {
            var events = new StringBuilder();
            var template = RenderTemplates.HasTemplates ? String.Format(@".render(""#{0}"", {{ event: ""autocomplete"", item: ""item.autocomplete"" }});", PrefixTemplateID(TemplateID)) : ";";

            foreach (var key in Scripts.Keys)
            {
                events.AppendFormat("{0}: {1}{2}", key, Scripts[key], key == Scripts.Keys.Last() ? String.Empty : ",");
            }

            writer.WriteLine("\t$('#{0}').autocomplete({{ delay: {1}, minLength: {2}, disabled: {3}, {4} }}){5}",
                ClientID, Delay, Length, Enabled ? "false" : "true", events.ToString(), template);

#if DEBUG
            writer.WriteLine();
#endif
        }

        public override void RaiseCallbackEvent(string arguments)
        {
            base.RaiseCallbackEvent(arguments);

            if (Page.IsCallback && Arguments.Event == jQNetEvents.source)
            {
                var handler = (jQNetHandler)Events[callback_key];

                if (Arguments != null)
                {
                    Arguments.Cancel = handler == null;
                }

                if (handler != null)
                {
                    handler(this, Arguments);
                }

                Arguments.IsHandled = true;
            }
        }

        protected override string PrefixTemplateID(string id)
        {
            return String.Format("ac_{0}", id);
        }

        protected override void OnInit(EventArgs e)
        {   /// the base class needs to init the event scripts and templates
            base.OnInit(e);

            var _debug = false;
#if DEBUG
            _debug = true;
#endif
            if (ClientScriptSource == undefined)
            {

                ClientScriptSource = String.Format(@"function(req, data) {{
                    var terms = [], tb = this.element, ul = tb.autocomplete('widget'), _data = tb.cache('get', '{0}') || {{}}, tmp = null;
                    if(_data[req.term] !== undefined) {{
                        data(_data[req.term]); return;
                    }}
                    tb.callback({{ event: 'source', args: req }}, function(result) {{
                        if(!result.canceled && result.type === 'json' && result.data.length > 0) {{
                            if((tmp = ul.children(['li:contains(', req.term, ')'].join('""'))).length > 0) {{
                                tmp.each(function(i, term) {{
                                    terms.push($(term).text());
                                }});
                            }}
                            if(terms.length >= result.data.length) {{
                                data(terms);
                            }} else {{
                               _data[req.term] = result.data;
                                tb.cache('set', '{0}', _data, true);
                                data(result.data);
                            }}
                        }} else {{ data([]); }}
                    }}, {2});
                }}", ID, "}", _debug ? error : undefined);

            }
        }
        #endregion

        protected override void RenderBeginTag(HtmlTextWriter writer)
        {
            if (Text.IsNotNullOrEmpty())
            {
                Attributes["value"] = Text;
            }

            if (!Width.IsEmpty)
            {
                Style.Add("width", Width.ToString());
            }

            base.RenderBeginTag(writer);
        }

        protected override void RenderEndTag(HtmlTextWriter writer)
        {
            writer.Write(" />");
        }
    }
}
